package folder

import (
	"context"
	"fmt"
	"github.com/goccy/go-json"
	"github.com/h2non/filetype"
	"github.com/shirou/gopsutil/v3/disk"
	"gitlab.local/DO-module/new-filemanage-module/src/core/middleware"
	"gitlab.local/DO-module/new-filemanage-module/src/service/filesystem/filenavigation"
	"gitlab.local/TerraMaster/tos-modules/folder"
	"gitlab.local/golibrary/picture/heic"
	"math"

	//share "gitlab.local/TerraMaster/tos-modules/share"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/controller/errcode"
	//"gitlab.local/DO-module/new-filemanage-module/src/core/fileoperate"
	"gitlab.local/DO-module/new-filemanage-module/src/service/file_safeBox"
	"gitlab.local/TerraMaster/tos-modules/fileoperate"

	"github.com/gin-gonic/gin"
	"github.com/gogf/gf/errors/gerror"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"gitlab.local/DO-module/new-filemanage-module/src/core/sqlitedb/sqlitemodel"
	"gitlab.local/golibrary/filedownload"
	"gitlab.local/golibrary/linux"
	"gitlab.local/golibrary/user"
	"gitlab.local/golibrary/utils"
	"golang.org/x/sys/unix"
	"gopkg.in/ini.v1"
)

// CheckNFSSMBFolder 挂载远程文件夹时检查 smb nfs远程文件夹是否有效。
func (f *Folder) CheckNFSSMBFolder(fileType, path, username, password string) error {
	if fileType == "nfs" {
		d := strings.Split(path, ":")
		if len(d) < 2 || strings.TrimSpace(d[0]) == "" || strings.TrimSpace(d[1]) == "" {
			return gerror.NewCode(errcode.SourcePathInvalid)
		}
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		//refer https://www.lixueduan.com/posts/go/exex-cmd-timeout/ 复杂的shell超时无效使用拆分的方式
		cmd := fmt.Sprintf("showmount -e --no-headers %s ", d[0])
		if res, err := utils.ShellWithContext(ctx, cmd); err != nil {
			return gerror.NewCode(errcode.SourcePathInvalid)
		} else {
			resultList := strings.Split(res, "\n")
			for _, v := range resultList {
				pathList := strings.Split(v, " ")
				if pathList[0] == d[1] {
					return nil
				}
			}
			return gerror.NewCode(errcode.SourcePathInvalid)
		}
	} else {

		path = strings.TrimLeft(path, "//")
		info := strings.Split(path, "/")
		if len(info) < 2 || strings.TrimSpace(info[0]) == "" || strings.TrimSpace(info[1]) == "" {
			return gerror.NewCode(errcode.SourcePathInvalid)
		}
		//step 1 验证用户名和密码
		//	smbclient  \\\\10.18.13.111\\   -U feng  Admin123  -L 10.18.13.111 -g
		result, _ := utils.ShellExec(fmt.Sprintf(`smbclient  \\\\%s\\   -U %s %s   -L %s -g | awk  'NR==1{print}' `, info[0], username, password, info[0]))

		if strings.Contains(result, "NT_STATUS_LOGON_FAILURE") {
			return gerror.NewCode(errcode.SmbAuthError)
		}

		//step 2 验证文件夹
		//smbclient  \\\\10.18.13.119\\   -U admin  pwd@090819  -L 10.18.13.119 -g | awk -F\| '{print $2}'
		//res, err := utils.ShellExec(fmt.Sprintf("smbclient -L   %s -t 1 --user %s --password %s -g  | awk -F\\| '{print $2}'| egrep '^%s$'", info[0], username, password, info[1]))
		res, err := utils.ShellExec(fmt.Sprintf(`smbclient  \\\\%s\\   -U %s  %s  -L %s -g | awk -F\| '{print $2}'| egrep '^%s$' | awk 'END{print}' `, info[0], username, password, info[0], info[1]))
		if err != nil {
			return gerror.NewCode(errcode.SourcePathInvalid)
		}
		if strings.TrimSpace(res) == info[1] {
			return nil
		}
		return gerror.NewCode(errcode.SourcePathInvalid)
	}
}

// VerifyQuota 验证用户的配额   文件大小，用户，目标路径
func (f *Folder) VerifyQuota(size int64, username, path string) error {
	var (
		overUserQuota   = gerror.New("over user quota")
		overFolderQuota = gerror.New("over folder quota")
		overVolumeQuota = gerror.New("over volume capacity")
	)
	s := strings.Split(path, "/")
	if len(s) < 2 {
		return gerror.New(fmt.Sprintf("invalid param %s", path))
	}
	volume := s[1]
	if volume == "home" {
		cmd := fmt.Sprintf(`df-json | egrep '(%s)$'| awk '{print $1}' | awk 'END{print}'`, volume)
		result, _ := utils.ShellExec(cmd)
		//result, _ := utils.ShellUserExec("abc", cmd)

		device := strings.TrimSpace(result)
		log.Println("device", device)
		cmd = fmt.Sprintf(`df-json | grep '%s' | egrep '(Volume[0-9]+)$' | awk '{print $8}'`, device)
		r, _ := utils.ShellExec(cmd)
		volume = strings.TrimLeft(strings.TrimSpace(r), "/")
	}

	//step1 验证用户ext4配额
	//在测试的时候发现quota -u '%s' --format=vfsv1 -w --hide-device --show-mntpoint -l | grep '%s' | awk '{print $2,$4}' 这个命令无效要加上v
	cmd := fmt.Sprintf(`quota -uv '%s' --format=vfsv1 -w --hide-device --show-mntpoint -l | grep '%s' | awk '{print $2,$4}'`, username, volume)
	if result, _ := utils.ShellExec(cmd); result != "" {
		//包括*表示已经超过配额了
		if strings.Contains(result, "*") {
			return overUserQuota
		}
		result = strings.TrimSpace(result)
		r := strings.Split(result, " ")
		if len(r) < 2 {
			return overUserQuota
		}
		//没有配额，capacity的容量为0
		capacity := cast.ToInt64(r[1]) * 1024
		if capacity != 0 {
			used := cast.ToInt64(r[0]) * 1024
			if used+size >= capacity {
				return overUserQuota
			}
		}

	}
	var share sqlitemodel.Share
	_ = f.db.DbGet().Table("share").Where("mntpath = ?", path).Select("ecryptfs,device,foldername").Find(&share)
	if share.Ecryptfs == 1 {
		path = filepath.Join("/", share.Device, fmt.Sprintf("@%s@", share.FolderName))
	}
	//step2 btrfs 文件限额
	used, limit, b := utils.BtrfsShareUserQuota(path)
	if limit != 0 {
		if b {
			return overFolderQuota
		}
		if used+size >= limit {
			return overFolderQuota
		}
	}
	//step文件系统的限制
	cmd = fmt.Sprintf(`df-json | egrep "(%s)$" |awk '{print $5}'`, volume)
	res, _ := utils.ShellExec(cmd)
	avail := cast.ToInt64(strings.TrimSpace(res)) * 1024
	if size >= avail {
		return overVolumeQuota
	}

	return nil

}

// FileUpload 上传文件,首次上传，当总片索引>当前片索引，
// 会在保存路径下生成一个.tmp-开头的文件，同时会有一个goroutine监听文件大小变化(间隔10s)，
// 当总片索引<=当前片索引则上传完成，把.tmp-开头的文件重命名为上传文件名
// file 文件流
// dir 保存路径
// totalIndex 总片索引
// index 当前片索引
// size 上传文件总容量
// uname 用户名
// autoDelete 自动删除失败文件
// lastModTime 最后修改时间(秒级时间戳)
func (f *Folder) FileUpload(file folder.HttpFile, dir string, totalIndex, index uint64, autoDelete bool, size int64, uname string, lastModTime int64) (string, error) {
	p, err := os.Stat(dir)
	if err != nil {
		return "", gerror.Wrap(err, "FileUpload")
	}
	if !p.IsDir() {
		return "", errors.New("the save path is not a folder")
	}

	//判断磁盘空间是否充足
	if index == 1 {
		var s sqlitemodel.Share
		f.db.DbGet().Table("share").Where("mntpath = ?", dir).Select("quota_flag").Find(&s)
		var pathFreeSize uint64
		//根据是否开启磁盘配额来查询目录的剩余空间
		if s.QuotaFlag == 1 {
			used, limit, b := utils.BtrfsShareUserQuota(dir)
			if b {
				return "", errors.New("Insufficient storage space")
			}
			free := limit - used
			pathFreeSize = uint64(free * 8)
		} else {
			pathFreeSize, err = utils.GetPathSize(dir)
			if err != nil {
				return "", errors.New("FileUpload1")
			}
		}

		if uint64(size) > pathFreeSize {
			return "", gerror.NewCode(errcode.InsufficientHardDiskCapacity)
		}
	}

	a := f.Acl.IsWrite(dir, uname)
	//b, err := f.Acl.GetAll(dir)
	//fmt.Println(b, err)
	if !a {
		return "", gerror.NewCode(errcode.PermissionDenied)
	}

	if f.isXfsVolume(dir) {
		//xfs文件上传方式
		return f.FileUploadXfs(file, dir, totalIndex, index, uname, lastModTime)
	}
	//常规上传
	return f.FileUploadConvention(file, dir, totalIndex, index, autoDelete, size, uname, lastModTime)
}

// FileUploadConvention 常规文件上传方式
func (f *Folder) FileUploadConvention(file folder.HttpFile, dir string, totalIndex, index uint64, autoDelete bool, size int64, uname string, lastModTime int64) (string, error) {
	//判断容量限制
	if index == 1 && f.fOptGetter.VolumeUserQuota(dir, uname, size) {
		return "", errors.New("drive space alert")
	}

	//临时文件前缀
	fileN := filepath.Base(file.Filename())
	//临时文件路径
	fileName := filepath.Join(dir, constant.IgnoreFilePre+fileN)
	src, err := file.Open()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention")
	}
	defer src.Close()

	if index > 1 && !utils.Exists(fileName) { //当索引值大于1时，判断临时文件是否存在，不存在为上传中断，上传失败
		return "", errors.New("file upload error, previous section of file missing")
	}

	if index == 1 { //重新开始，清除残留文件
		_ = os.Remove(fileName)
	}

	//_ = utils.SwitchUserRight(uname)

	fi, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadConvention1")
	}
	defer fi.Close()

	_ = unix.Flock(int(fi.Fd()), unix.LOCK_EX)

	if autoDelete {
		//文件删除计时器,一分钟后没请求则删除临时文件,有请求则重置计时器
		_ = f.reloadFileDelTimer(fileName, time.Minute, func() { _ = os.RemoveAll(fileName) })
	}

	if _, err = io.Copy(fi, src); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention2")
	}

	//判断是否上传结束
	if index != totalIndex {
		return fileName, nil
	}

	if autoDelete {
		//上传成功停止计时器
		_ = f.stopFileDelTimer(fileName)
	}

	stat, err := os.Stat(fileName)
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention3")
	}

	//判断文件大小是否相等
	if stat.Size() != size {
		_ = os.Remove(fileName)
		return "", errors.New("file sizes vary and files are missing")
	}

	newFileName, err := utils.AutoFolderName(filepath.Join(dir, fileN))
	if err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention4")
	}

	//重命名文件
	if err = os.Rename(fileName, newFileName); err != nil {
		_ = os.Remove(fileName)
		return "", gerror.Wrap(err, "FileUploadConvention5")
	}

	//修改文件拥有者
	_ = f.Acl.Chown(newFileName, uname)
	//修改文件最后修改时间
	_ = f.modifyLastModTime(newFileName, lastModTime)
	//继承父级acl权限
	_ = f.Acl.InheritedPermissions(newFileName, "", false)

	return newFileName, nil
}

// FileUploadXfs xfs文件上传方式
func (f *Folder) FileUploadXfs(file folder.HttpFile, dir string, totalIndex, index uint64, uname string, lastModTime int64) (string, error) {
	if index == 1 {
		srcPath, err := utils.AutoFolderName(filepath.Join(dir, filepath.Base(file.Filename())))
		if err != nil {
			return "", gerror.Wrap(err, "FileUploadXfs")
		}

		_ = utils.SwitchUserRight()

		fi, err := os.OpenFile(srcPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err != nil {
			return "", errors.New("FileUploadXfs1")
		}
		if f.xfsUpdate == nil {
			f.xfsUpdate = make(map[string]*os.File)
		}
		f.xfsUpdate[file.Filename()] = fi
		_ = unix.Flock(int(fi.Fd()), unix.LOCK_EX)
	}

	fi, b := f.xfsUpdate[file.Filename()]
	if !b {
		return "", errors.New("loss of previous data")
	}

	newFilePath := fi.Name()

	_ = f.reloadFileDelTimer(newFilePath, time.Minute, func() { _ = fi.Close() })
	src, err := file.Open()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadXfs2")
	}

	_, err = io.Copy(fi, src)
	_ = src.Close()
	if err != nil {
		return "", gerror.Wrap(err, "FileUploadXfs3")
	}

	if index < totalIndex {
		//上传未结束
		return newFilePath, nil
	}
	//上传结束
	_ = fi.Close()
	delete(f.xfsUpdate, file.Filename())

	_ = f.stopFileDelTimer(newFilePath)
	//修改文件拥有者
	_ = f.Acl.Chown(newFilePath, uname)
	//修改文件最后修改时间
	_ = f.modifyLastModTime(newFilePath, lastModTime)
	//继承父级acl权限
	_ = f.Acl.InheritedPermissions(newFilePath, "", false)

	return newFilePath, nil
}

// modifyLastModTime 修改文件最后修改时间(小于或等于0则不修改)
// lastModTime 最后修改时间(秒级时间戳)
func (f *Folder) modifyLastModTime(path string, lastModTime int64) error {
	if lastModTime <= 0 {
		return nil
	}
	t := time.Unix(lastModTime, 0)
	return os.Chtimes(path, t, t)
}

// isXfsVolume 判断路径是否为xfs卷
func (f *Folder) isXfsVolume(path string) bool {
	str := regexp.MustCompile(`^/Volume\d+($|/)`).FindString(path)
	if str == "" {
		return false
	}
	str = "/" + strings.Trim(str, "/")
	res, _ := utils.ShellExec(fmt.Sprintf(`df-json | grep '%s$' | grep xfs`, str))
	return strings.TrimSpace(res) != ""
}

// stopFileDelTimer 终止延迟任务计时器
// path 路径
func (f *Folder) stopFileDelTimer(path string) error {
	f.delRWMutex.RLock()
	d, b := f.delTimer[path]
	f.delRWMutex.RUnlock()
	if b {
		_ = d.t.Stop()
		d.stop <- struct{}{}
	}
	return nil
}

// reloadFileDelTimer 延迟任务计时器,如果以存在计时器则重置，没有则添加
// path 路径
// t 延迟时间
// fun 触发逻辑
func (f *Folder) reloadFileDelTimer(path string, t time.Duration, fun func()) error {
	f.delRWMutex.RLock()
	d, b := f.delTimer[path]
	f.delRWMutex.RUnlock()
	if b {
		_ = d.t.Stop()
		_ = d.t.Reset(t)
	} else {
		f.delRWMutex.Lock()
		f.delTimer[path] = f.timer(path, t, fun)
		f.delRWMutex.Unlock()
	}
	return nil
}

func (f *Folder) timer(path string, t time.Duration, fun func()) *FileTimer {
	ft := &FileTimer{
		t:    time.NewTimer(t),
		stop: make(chan struct{}, 1),
	}
	go func() {
		select {
		case <-ft.t.C:
			fun()
		case <-ft.stop: //终止信号
		}
		f.delRWMutex.Lock()
		delete(f.delTimer, path)
		f.delRWMutex.Unlock()
	}()
	return ft
}

func (f *Folder) UpdateFile(filePath string, fileHeader *multipart.FileHeader) (int64, error) {
	fileNew, err := fileHeader.Open()
	defer fileNew.Close()
	if err != nil {
		return -1, gerror.NewCode(errcode.InvalidFile, filePath)
	}
	fileOld, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, os.ModePerm)
	if err != nil {
		return -1, gerror.Wrap(err, "EditFile")
	}
	defer fileOld.Close()
	return io.Copy(fileOld, fileNew)
}

// MultimediaPlay 多媒体播放
func (f *Folder) MultimediaPlay(uname, path string, c *gin.Context) error {
	stat, err := os.Stat(path)
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay")
	}
	if stat.IsDir() {
		return errors.New("the path is not a file")
	}
	if uname != "" {
		//如果用户名称存在则判断权限，不存在则不判断
		if !f.Acl.IsRead(path, uname) {
			return gerror.NewCode(errcode.PermissionDenied)
		}
	}

	var size int64 = 32774 //一般文件只需前262个字符，iso类大文件可能需要更大
	if stat.Size() < size {
		size = stat.Size()
	}
	file, err := os.Open(path)
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay1")
	}
	buf := make([]byte, size)
	if _, err = file.Read(buf); err != nil {
		_ = file.Close()
		return gerror.Wrap(err, "MultimediaPlay2")
	}
	if !f.CheckFileClass(buf, path) {
		_ = file.Close()
		return errors.New("file type not supported")
	}

	var baseName string

	//heif图像特殊处理
	if filetype.IsExtension(buf, "heif") {
		webp, err := f.heicToTmpWebp(file)
		_ = file.Close() //关闭原文件句柄
		if err != nil {
			return gerror.Wrap(err, "MultimediaPlay2")
		}
		file, err = os.Open(webp) //覆盖原文件句柄
		if err != nil {
			return gerror.Wrap(err, "MultimediaPlay3")
		}
		baseName = strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + filepath.Ext(webp)
	} else {
		baseName = filepath.Base(path)
	}

	info, err := file.Stat()
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay4")
	}

	c.Header("Content-Disposition", fmt.Sprintf(`inline;filename="%s";filename*=utf-8;`, baseName))
	http.ServeContent(c.Writer, c.Request, baseName, info.ModTime(), file)
	_ = file.Close()

	return nil
}

func (f *Folder) IsVideo(buf []byte, path string) bool {
	if filetype.IsVideo(buf) {
		return true
	}
	out, err := utils.ShellExec(fmt.Sprintf(`file --mime-type '%s'`, path))
	if err != nil {
		return false
	}
	return strings.Contains(out, " video/")
}

// CheckFileClass 获取文件归限制
func (f *Folder) CheckFileClass(buf []byte, path string) bool {
	//图像,音频,视频,pdf
	return filetype.IsImage(buf) || filetype.IsAudio(buf) || filetype.IsExtension(buf, "pdf") || f.IsVideo(buf, path)
}

// heicToTmpWebp heic转换为webp
func (f *Folder) heicToTmpWebp(filenameIn *os.File) (string, error) {
	md5 := utils.GetIoMd5(filenameIn)
	if md5 == "" {
		return "", errors.New("get md5 error")
	}
	dir := filepath.Join(linux.GetMainVolume(), "@thumbnail", md5)
	//缩略图路径
	distSrc := filepath.Join(dir, fmt.Sprintf("%s.webp", md5))
	if utils.Exists(distSrc) {
		return distSrc, nil
	}
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}
	create, err := os.Create(distSrc)
	if err != nil {
		return "", err
	}
	defer create.Close()
	if err = heic.ToWebp(filenameIn, create); err != nil {
		return "", err
	}
	return distSrc, nil
}

// RunAllRemoteDownloadTask 运行全部远程下载任务(优先下载进度最高的任务)
func (f *Folder) RunAllRemoteDownloadTask(username string) error {
	q, b := f.remoteDownloadQueue[username]
	if !b {
		return nil
	}

	q.Lock()
	defer q.Unlock()

	//先将全部任务改成等待状态(正在运行的不操作)
	for _, v := range q.queue {
		if v.Status == filedownload.Running {
			continue
		}
		_ = v.Waiting()
	}

	num := f.remoteLimitNum - f.remoteRunNum
	if num <= 0 {
		return nil
	}

	sort.SliceStable(q.queue, func(i, j int) bool {
		if q.queue[i].Status != filedownload.Pausing {
			return false
		}
		if q.queue[j].Status != filedownload.Pausing {
			return false
		}
		rate1, err := q.queue[i].Rate()
		if err != nil {
			return false
		}
		rate2, err := q.queue[j].Rate()
		if err != nil {
			return false
		}
		return rate1 > rate2
	})

	for i := 0; i < int(num); i++ {
		f.remoteRunNum++
		_ = q.queue[i].Run()
	}

	return nil
}

// RunRemoteDownloadTask 运行远程下载任务
func (f *Folder) RunRemoteDownloadTask(taskId, username string) error {
	q, b := f.remoteDownloadQueue[username]
	if !b {
		return nil
	}

	q.Lock()
	defer q.Unlock()

	var down *filedownload.Download
	var key int

	for k, v := range q.queue {
		if v.TaskId == taskId {
			down = v
			key = k
			break
		}
	}

	if down == nil {
		return nil
	}

	//先判断正在运行任务数量是否到达限制，如果是，则把当前任务改成等待状态，并把任务前置（插入到首个未运行或等待状态任务前）
	if f.remoteRunNum < f.remoteLimitNum {
		f.remoteRunNum++
		return down.Run()
	}

	//任务已满
	//先把任务从队列中拿出,在插入到首个等待任务前
	_ = down.Waiting()
	if key+1 >= len(q.queue) {
		//k为最后元素
		q.queue = q.queue[:key]
	} else {
		q.queue = append(q.queue[:key], q.queue[key+1:]...)
	}

	for k, v := range q.queue {
		//获取出首个未运行或等待状态任务index,在前方插入
		if v.Status == filedownload.Waiting {
			q.queue = append(q.queue, &filedownload.Download{})
			copy(q.queue[k+1:], q.queue[k:])
			q.queue[k] = down
			break
		}
	}

	return nil
}

// PauseRemoteDownloadTask 暂停远程下载任务
func (f *Folder) PauseRemoteDownloadTask(taskId, uname string) error {
	q, b := f.remoteDownloadQueue[uname]
	if !b {
		return nil
	}

	q.Lock()
	defer q.Unlock()

	for _, v := range q.queue {
		if v.TaskId == taskId {
			return v.Pause()
		}
	}

	return nil
}

// PauseAllRemoteDownloadTask 暂停所有远程下载任务
func (f *Folder) PauseAllRemoteDownloadTask(uname string) error {
	q, b := f.remoteDownloadQueue[uname]
	if !b {
		return nil
	}

	q.Lock()

	for _, v := range q.queue {
		_ = v.Pause()
	}

	q.Unlock()

	return nil
}

// BreakRemoteDownloadAll 清除全部远程下载任务
func (f *Folder) BreakRemoteDownloadAll(uname string) error {
	q, b := f.remoteDownloadQueue[uname]
	if !b {
		return nil
	}

	q.Lock()

	for _, v := range q.queue {
		_ = v.Termination()
	}

	q.queue = make([]*filedownload.Download, 0)

	q.Unlock()

	return nil
}

// BreakRemoteDownload 清除单个远程下载任务
func (f *Folder) BreakRemoteDownload(taskId, uname string) error {
	q, b := f.remoteDownloadQueue[uname]
	if !b {
		return nil
	}

	q.Lock()

	for k, v := range q.queue {
		if v.TaskId == taskId {
			_ = v.Termination()
			//删除任务
			if k+1 >= len(q.queue) {
				//k为最后元素
				q.queue = q.queue[:k]
			} else {
				q.queue = append(q.queue[:k], q.queue[k+1:]...)
			}
			break
		}
	}

	q.Unlock()

	return nil
}

// ClearCompleteLogs 清除已完成的任务
func (f *Folder) ClearCompleteLogs(userName string) error {
	q, b := f.remoteDownloadQueue[userName]
	if !b {
		return nil
	}

	q.Lock()

	for i := 0; i < len(q.queue); {
		if q.queue[i].Status == filedownload.Finish {
			//删除任务数据
			if i+1 >= len(q.queue) {
				//k为最后元素
				q.queue = q.queue[:i]
			} else {
				q.queue = append(q.queue[:i], q.queue[i+1:]...)
			}
		} else {
			i++
		}
	}

	q.Unlock()

	return nil
}

// GetPathSize 获取目录剩余可用容量
func (f *Folder) GetPathSize(dir string) (uint64, error) {
	return utils.GetPathSize(dir)
}

// GetThumbnail 获取缩略图
func (f *Folder) GetThumbnail(path string, c *gin.Context) error {

	// 小缩略图
	mini := c.Query("mini")
	if mini == "1" {

		isInSpecialPath := func(path string) bool {

			var specialPath = []string{"@usb", "@iscsi"}
			for _, k := range specialPath {
				if strings.Contains(path, k) {
					return true
				}
			}
			dfs, err := disk.Partitions(false)
			if err != nil {
				return false
			}
			for _, df := range dfs {
				if df.Fstype == "xfs" && strings.Contains(path, df.Mountpoint) {
					return true
				}
			}
			return false
		}

		GetVolumeInPath := func(path string) string {

			if isInSpecialPath(path) {
				return "/tmp"
			}

			paths := strings.Split(path, "/")
			if paths[1] == "home" {
				return linux.GetMainVolume()
			}

			return "/" + paths[1]
		}

		md5 := utils.Gethashfile(path)
		dir := filepath.Join(GetVolumeInPath(path), "@thumbnail", md5)
		if !utils.Exists(dir) {
			os.Mkdir(dir, 0664)
		}

		thumbnailPath := filepath.Join(dir, filepath.Base(path))
		if !utils.Exists(thumbnailPath) {

			// ffmpeg
			if _, err := utils.ShellWithString(fmt.Sprintf("ffmpeg -i '%s' -vf 'scale=iw*0.1:ih*0.1' '%s' -y", path, thumbnailPath)); err == nil {

				file, _ := os.Open(thumbnailPath)
				defer file.Close()
				io.Copy(c.Writer, file)
				return nil
			}
		} else {

			file, _ := os.Open(thumbnailPath)
			defer file.Close()
			io.Copy(c.Writer, file)
			return nil
		}
	}

	username, err := middleware.GetUserName(c)
	if err != nil {
		c.String(http.StatusBadRequest, "failed to get username")
		return err
	}
	rule := c.Query("rule")
	order := c.Query("order")
	id, _ := strconv.Atoi(c.Query("id"))
	go func(path, username, rule, order string, id int) {

		list, err := f.GetFoldersFilter(filepath.Dir(path), username, 0, 0, fileoperate.OrderRule(rule), order == "true", 0, context.Background())
		if err != nil {
			fmt.Println(err)
		}

		fileArr := make([]string, 0)
		for _, files := range list.Data {

			if files.FType == "image" {
				fileArr = append(fileArr, files.Path)
			}
		}

		if id >= 0 && id < len(fileArr) && fileArr[id] == path {

			// 同时后台生成前后两张缩略图
			if id >= 2 {

				f.Thumbnail(fileArr[id-1], context.Background())
				f.Thumbnail(fileArr[id-2], context.Background())
			}

			if id < len(fileArr)-3 {

				f.Thumbnail(fileArr[id+1], context.Background())
				f.Thumbnail(fileArr[id+2], context.Background())
			}

			fmt.Println("match")
		}
	}(path, username, rule, order, id)

	// f.thumbnailMutex.Lock()
	// defer f.thumbnailMutex.Unlock()

	src, err := f.Thumbnail(path, c.Request.Context())
	if err != nil {
		return gerror.Wrap(err, "GetThumbnail")
	}
	open, err := os.Open(src)
	if err != nil {
		return gerror.Wrap(err, "GetThumbnail1")
	}
	defer open.Close()
	info, err := open.Stat()
	if err != nil {
		return gerror.Wrap(err, "GetThumbnail2")
	}
	baseName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + filepath.Ext(src)
	c.Header("Content-Disposition", fmt.Sprintf(`inline;filename="%s";filename*=utf-8;`, baseName))
	//参数：响应上下文 请求上下文 响应资源名称 资源修改时间(如果修改时间相同，浏览器会做304状态码处理) 读取io句柄
	http.ServeContent(c.Writer, c.Request, baseName, info.ModTime(), open)
	return nil
}

func (f *Folder) GetVideoCover(path string, c *gin.Context) error {

	f.thumbnailMutex.Lock()
	defer f.thumbnailMutex.Unlock()

	stat, err := os.Stat(path)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		return errors.New("directory not supported")
	}
	res, err := utils.ShellExec(fmt.Sprintf("%s -p %s -t 1 -m 2", constant.ThumbnailTool, utils.StrSpecialLetters(path)))
	if err != nil {
		return err
	}

	res = strings.TrimSpace(res)
	open, err := os.Open(res)
	if err != nil {
		return gerror.Wrap(err, "GetVideoCover1")
	}
	defer open.Close()
	info, err := open.Stat()
	if err != nil {
		return gerror.Wrap(err, "GetVideoCover2")
	}
	baseName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + filepath.Ext(res)
	c.Header("Content-Disposition", fmt.Sprintf(`inline;filename="%s";filename*=utf-8;`, baseName))
	//参数：响应上下文 请求上下文 响应资源名称 资源修改时间(如果修改时间相同，浏览器会做304状态码处理) 读取io句柄
	http.ServeContent(c.Writer, c.Request, baseName, info.ModTime(), open)
	return nil
}

// Thumbnail 生成缩略图
func (f *Folder) Thumbnail(path string, context context.Context) (string, error) {
	stat, err := os.Stat(path)
	if err != nil {
		return "", err
	}
	if stat.IsDir() {
		return "", errors.New("directory not supported")
	}
	res, err := utils.ShellWithContext(context, fmt.Sprintf("%s -p %s -t 1", constant.ThumbnailTool, utils.StrSpecialLetters(path)))
	return strings.TrimSpace(res), err
}

func (f *Folder) unix(path string, userInfo *user.User) (unix string) {
	unix = "---"
	// 切换用户
	if err := utils.SwitchUserRight(userInfo.Name); err != nil {
		return
	}
	fileInfo, err := os.Stat(path)
	if err != nil {
		return
	}
	perm := fileInfo.Mode().Perm()
	// 根据是否为超级用户
	if userInfo.Gid == "0" {
		unix = perm.String()[1:4]
	} else {
		unix = perm.String()[7:10]
	}
	// fmt.Printf("Group: %s\n", perm.String()[4:7])
	return
}

// ReadRemoteConfig Read remote folder configuration
func (f *Folder) ReadRemoteConfig() (remoterMap map[string]Remoter, err error) {
	b, err := os.ReadFile(constant.RemoteFolderConfig)
	// 只要写入配置没问题，读的时候就不会有问题，不用处理异常
	_ = json.Unmarshal(b, &remoterMap)

	return remoterMap, err
}

// GetHomeListFilter 获取文件管理菜单列表文件夹(过滤处理)
func (f *Folder) GetHomeListFilter(userName string) ([]fileoperate.FoldersInfo, error) {
	//list := make([]share.FoldersInfo, 0)
	var list []fileoperate.FoldersInfo
	var remoterList []fileoperate.FoldersInfo

	u, err := user.Lookup(userName)
	if err != nil {
		return list, err
	}
	go func() {
		remoterMap, e := f.ReadRemoteConfig()
		if e != nil {
			return
		}
		for _, item := range remoterMap {
			info, e := f.fOptGetter.GetFolder(item.DestinationPath)
			if e != nil {
				continue
			}
			info.MountType = fileoperate.RemoteMount
			remoterList = append(remoterList, *info)
		}
	}()
	list, err = f.GetHomeList(userName, u.HomeDir)
	if err != nil {
		return list, gerror.Wrap(err, "GetHomeListFilter")
	}
	homeShow := f.homeShow()
	volumeSystem := f.volumeFileSystem()

	var wg sync.WaitGroup
	for k, v := range list {
		wg.Add(1)
		go func(k int, v fileoperate.FoldersInfo) {
			defer wg.Done()
			var acl string
			if strings.Index(v.Path, "@usb") > -1 || strings.Index(v.Path, "@drives") > -1 {
				acl = f.unix(v.Path, u)
			} else {
				acl, err = f.Acl.UserAcl(v.Path, userName)
				if err != nil {
					return
				}
			}
			//无权限
			if !strings.Contains(acl, "r") {
				list[k].Hidden = true
			}
			list[k].Permission = acl
			if (v.IsHome == 1 || v.Name == "homes") && !homeShow {
				list[k].Hidden = true
			}
			if v.IsHome != 1 {
				list[k].VolumePath, _ = utils.SwitchShowName(v.Path)
			} else {
				list[k].VolumePath = ""
			}
			list[k].Level = 1
			system, b := volumeSystem["/"+v.Volume]
			if b {
				list[k].FileSystem = system
			}
		}(k, v)
	}

	wg.Wait()

	// 自定义排序函数
	sort.Slice(list, func(i, j int) bool {
		//用户目录优先
		if list[i].IsHome == 1 {
			return true
		} else if list[j].IsHome == 1 {
			return false
		}

		if list[i].Name == "homes" && list[i].Path == "/home" {
			return true
		} else if list[j].Name == "homes" {
			return false
		}

		// 外接设备排后面
		// 外接设备排后面
		iIsExternal := extractSpecialToken(list[i].Path)
		jIsExternal := extractSpecialToken(list[j].Path)

		// 根据特殊标记的顺序进行排序
		iIsExternalPriority := getExternalPriority(string(iIsExternal))
		jIsExternalPriority := getExternalPriority(string(jIsExternal))
		if iIsExternalPriority > 1 || jIsExternalPriority > 1 {
			list[i].MountType = iIsExternal
			list[j].MountType = jIsExternal
			if iIsExternalPriority != jIsExternalPriority {
				return iIsExternalPriority < jIsExternalPriority
			}
			//挂载盘排序
			return list[i].Name < list[j].Name
		}

		// 如果以上条件都不满足，则按 Volume 排序
		if list[i].Volume != list[j].Volume {
			return list[i].Volume < list[j].Volume
		}
		//public排在相同的前面
		if list[i].Name == "public" {
			return true
		} else if list[j].Name == "public" {
			return false
		}

		// 如果相同条件下，按照文件夹名称字母顺序排序
		return list[i].Name < list[j].Name
	})
	if len(remoterList) > 0 {
		list = append(list, remoterList...)
	}
	return list, nil
}

// 辅助函数，用于确定外部设备的优先级
func getExternalPriority(token string) int {
	switch token {
	case "@usb":
		return 3
	case "@drives":
		return 4
	case "@disk":
		return 5
	case "@iscsi":
		return 6 // 最低优先级
	default:
		return 1 // 非外部设备
	}
}

// 提取字符串中的特殊标记，如果有多个，只返回第一个
func extractSpecialToken(s string) fileoperate.MountType {
	for _, token := range fileoperate.MountTypeMap {
		if strings.Contains(s, string(token)) {
			return token
		}
	}
	return ""
}

func (f *Folder) GetUSBListFilter(userName string) ([]fileoperate.FoldersInfo, error) {
	list := make([]fileoperate.FoldersInfo, 0)
	u, err := user.Lookup(userName)
	if err != nil {
		return list, err
	}

	list, err = f.GetHomeList(u.Name, filepath.Join("/home", userName))
	if err != nil {
		return list, gerror.Wrap(err, "GetHomeListFilter")
	}

	volumeSystem := f.volumeFileSystem()
	homeShow := f.homeShow()

	var wg sync.WaitGroup

	for k, v := range list {
		if !strings.Contains(v.Name, "usb") {
			continue
		}
		wg.Add(1)
		go func(k int, v fileoperate.FoldersInfo) {
			defer wg.Done()
			acl, err := f.Acl.UserAcl(v.Path, u.Name)
			if err != nil {
				return
			}
			//无权限
			if !strings.Contains(acl, "r") {
				list[k].Hidden = true
			}
			if list[k].FType == utils.Oexe {
				readlink, err := os.Readlink(list[k].Path)
				if err != nil {
					return
				}
				list[k].Path = readlink
			}
			list[k].Permission = acl
			if (v.IsHome == 1 || v.Name == "homes") && !homeShow {
				list[k].Hidden = true
			}
			if v.IsHome != 1 {
				list[k].VolumePath, _ = utils.SwitchShowName(v.Path)
			} else {
				list[k].VolumePath = ""
			}
			list[k].Level = 1
			system, b := volumeSystem["/"+v.Volume]
			if b {
				list[k].FileSystem = system
			}
		}(k, v)
	}

	wg.Wait()

	for k, v := range list {
		if v.IsHome == 1 && len(list) > 1 {
			list[0], list[k] = list[k], list[0]
			continue
		}
		if v.Name == "homes" && len(list) > 2 {
			list[1], list[k] = list[k], list[1]
			continue
		}
		if v.Name == "public" && len(list) > 3 {
			list[2], list[k] = list[k], list[2]
			continue
		}
	}

	if len(list) > 3 {
		sortArr := list[3:]
		sort.SliceStable(sortArr, func(i, j int) bool {
			return sortArr[j].Name > sortArr[i].Name
		})
	}

	return list, nil
}

// volumeFileSystem 获取卷对应的文件系统
func (f *Folder) volumeFileSystem() map[string]string {
	data := make(map[string]string)
	dfs, err := disk.Partitions(false)
	if err != nil {
		return data
	}
	for _, v := range dfs {
		data[v.Mountpoint] = v.Fstype
	}

	return data
}

// homeShow home目录是否显示
func (f *Folder) homeShow() bool {
	load, err := ini.Load(constant.UserAdvancePath)
	if err != nil {
		return true
	}
	return load.Section("userAdvanced").Key("homeDirectory").Value() == "1"
}

var VolumeRegexP *regexp.Regexp = regexp.MustCompile(`^/Volume\d+($|/)`)

// GetHomeList 获取文件管理菜单列表文件夹
func (f *Folder) GetHomeList(userName, homeDir string) ([]fileoperate.FoldersInfo, error) {
	data := make([]fileoperate.FoldersInfo, 0)

	homeVolume := f.fOptGetter.GetHomeVolume()

	if homeVolume != "" {
		//获取主卷
		mainVolume := strings.TrimPrefix(homeVolume, "/")
		fd, err := f.fOptGetter.GetFolder(homeDir)
		if err == nil {
			fd.IsHome = 1
			fd.Volume = mainVolume
			data = append(data, *fd)
		}

		if f.isAdminGroup(userName) {
			homes, err := f.fOptGetter.GetFolder(filepath.Join("/home"))
			if err == nil {
				homes.IsHome = 2
				homes.Name = "homes"
				homes.Volume = mainVolume
				data = append(data, *homes)
			}
		}
	}

	shareList, _ := f.fOptGetter.GetShareList()

	for _, v := range shareList {
		v.Name = strings.Trim(v.Name, "@") //去除首尾多余@符号
		v.Volume = strings.TrimPrefix(strings.TrimSuffix(VolumeRegexP.FindString(v.Path), "/"), "/")

		data = append(data, *v)
	}

	/*
		【选择一个目录，所有用户的home目录应该显示在homes文件夹下】https://www.tapd.cn/30463495/bugtrace/bugs/view?bug_id=1130463495001017489
	*/
	//这里要循环其他用户家目录
	//filter, _ := f.GetFoldersFilter("/home", userName, 0, 0, fileoperate.Nature, false, 0, nil)
	//for _, f := range filter.Data {
	//	if f.Name != userName {
	//		data = append(data, f)
	//	}
	//
	//}

	return data, nil
}

// isAdminGroup 是否是admin用户组成员
func (f *Folder) isAdminGroup(uname string) bool {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return false
	}
	arr, err := lookup.GroupIds()
	if err != nil {
		return false
	}
	group, err := user.LookupGroup("admin")
	if err != nil {
		return false
	}
	for _, v := range arr {
		if v == group.Gid {
			return true
		}
	}
	return false
}

// IsRootAdmin 是否是超级管理员用户
func (f *Folder) IsRootAdmin(uname string) (bool, error) {
	return os.Getenv("admin") == uname, nil
}

// IsAdmin 是否是管理员组成员
func (f *Folder) IsAdmin(uname string) (bool, error) {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return false, err
	}
	group, err := user.LookupGroup("admin")
	if err != nil {
		return false, err
	}
	ids, err := lookup.GroupIds()
	if err != nil {
		return false, err
	}

	for _, v := range ids {
		if v == group.Gid {
			return true, nil
		}
	}

	return false, nil
}

// GetFoldersFilter 获取路径下文件及文件夹信息（带过滤条件和额外处理）
func (f *Folder) GetFoldersFilter(dir, username string, limit, page int, rule fileoperate.OrderRule, order bool, depth int, c context.Context) (*fileoperate.Folders, error) {
	data := &fileoperate.Folders{
		Total: 0,
		Data:  make([]fileoperate.FoldersInfo, 0),
	}
	fn := filenavigation.FileNav{}
	conf, err := fn.GetRecentlyConf(username)
	if err != nil {
		return data, nil
	}

	//保险箱特殊处理
	lookup, err := user.Lookup(username)
	if err != nil {
		return nil, err
	}

	safeBoxSrc := fmt.Sprintf(file_safeBox.MountSafeBoxDir, lookup.HomeDir)
	folders, err := f.fOptGetter.GetFolders(dir, limit, page, rule, order, conf.NoShowHiddenFolder, safeBoxSrc, fn.IsMount(safeBoxSrc))
	if err != nil {
		return data, nil
	}

	data.Total = folders.Total
	if limit > 0 {
		data.TotalPage = customRoundUp(data.Total, limit)
	}
	for _, v := range folders.Data {
		if v.Path == safeBoxSrc {
			continue
		}
		//显示路径
		v.VolumePath, _ = utils.SwitchShowName(v.Path)
		v.IsCollect = fn.IsCollectList(username, v.Path) //是否收藏
		//层级
		v.Level = utils.FileLevel(v.Path)
		if depth > 1 {
			filter, err := f.GetFoldersFilter(v.Path, username, limit, page, rule, order, depth-1, c)
			if err == nil {
				v.ChildFoldersInfo = filter.Data
			}
		}
		data.Data = append(data.Data, v)
	}

	return data, nil
}

// EditFile 编辑文件
func (f *Folder) EditFile(path, content string) error {
	file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, os.ModePerm)
	if err != nil {
		return gerror.Wrap(err, "EditFile")
	}
	_, err = file.WriteString(content)
	_ = file.Close()
	return err
}

func customRoundUp(dividend, divisor int) int {
	quotient := float64(dividend) / float64(divisor)
	if quotient < 1 {
		return 1 // 如果商小于1，则返回1
	}
	return int(math.Ceil(quotient)) // 否则，向上取整
}

// GetDirTree 获取文件管理左侧目录树
func (f *Folder) GetDirTree(uname, path string, c context.Context) ([]fileoperate.FoldersInfo, error) {
	list := make([]fileoperate.FoldersInfo, 0)

	path = filepath.Dir(path)
	var compile *regexp.Regexp
	if strings.Contains(uname, "/") || strings.Contains(uname, "@") {
		//域用户
		compile = regexp.MustCompile(`^(/Volume\d+/(@usb/usbshare\d+|@iso|@iscsi/[^/]+|@eSATA|[^/]+/?)|(/home/[^/]+/[^/]+/?))`) //提取共享文件夹或home目录路径
	} else {
		compile = regexp.MustCompile(`^(/Volume\d+/(@usb/usbshare\d+|@iso/[^/]+|@iscsi/[^/]+|@eSATA|[^/]+/?)|(/home/[^/]+/?))`) //提取共享文件夹或home目录路径
	}
	matchString := compile.FindString(path)
	if matchString == "" {
		return list, nil
	}
	list, err := f.GetHomeListFilter(uname)
	if err != nil {
		return list, gerror.Wrap(err, "GetDirTree1")
	}
	p := matchString
	d := f.treeDir(p, uname, &list, c)
	if d == nil {
		return list, nil
	}
	for _, v := range strings.Split(strings.TrimPrefix(path, matchString), "/") {
		if v == "" {
			continue
		}
		p = filepath.Join(p, v)
		d = f.treeDir(p, uname, d, c)
		if d == nil {
			break
		}
	}
	return list, nil
}

func (f *Folder) treeDir(parentPath, uname string, l *[]fileoperate.FoldersInfo, c context.Context) *[]fileoperate.FoldersInfo {
	list := *l
	for k, v := range list {
		a := strings.TrimSuffix(v.Path, "/")
		b := strings.TrimSuffix(parentPath, "/")
		if a == b {
			filter, err := f.GetFoldersFilter(v.Path, uname, 0, 0, "", false, 0, c)
			if err != nil {
				return nil
			}
			var data []fileoperate.FoldersInfo
			for _, val := range filter.Data {
				if val.FType == utils.Folder {
					data = append(data, val)
				}
			}
			list[k].ChildFoldersInfo = data
			return &list[k].ChildFoldersInfo
		}
	}
	return nil
}
